{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 🤗 Huggingface vs ⚡ FastEmbed️\n",
    "\n",
    "Comparing the performance of Huggingface's 🤗 Transformers and ⚡ FastEmbed️ on a simple task on the following machine: Apple M2 Max, 32 GB RAM\n",
    "\n",
    "## 📦 Imports\n",
    "\n",
    "Importing the necessary libraries for this comparison."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-30T00:33:35.753669Z",
     "start_time": "2024-03-30T00:33:34.371658Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/Users/joein/work/qdrant/fastembed/venv/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "from typing import Callable\n",
    "\n",
    "import torch.nn.functional as F\n",
    "from fastembed import TextEmbedding\n",
    "import matplotlib.pyplot as plt\n",
    "from transformers import AutoModel, AutoTokenizer"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 📖 Data\n",
    "\n",
    "data is a list of strings, each string is a document."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-30T00:33:35.766679Z",
     "start_time": "2024-03-30T00:33:35.755112Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "12"
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "documents: list[str] = [\n",
    "    \"Chandrayaan-3 is India's third lunar mission\",\n",
    "    \"It aimed to land a rover on the Moon's surface - joining the US, China and Russia\",\n",
    "    \"The mission is a follow-up to Chandrayaan-2, which had partial success\",\n",
    "    \"Chandrayaan-3 will be launched by the Indian Space Research Organisation (ISRO)\",\n",
    "    \"The estimated cost of the mission is around $35 million\",\n",
    "    \"It will carry instruments to study the lunar surface and atmosphere\",\n",
    "    \"Chandrayaan-3 landed on the Moon's surface on 23rd August 2023\",\n",
    "    \"It consists of a lander named Vikram and a rover named Pragyan similar to Chandrayaan-2. Its propulsion module would act like an orbiter.\",\n",
    "    \"The propulsion module carries the lander and rover configuration until the spacecraft is in a 100-kilometre (62 mi) lunar orbit\",\n",
    "    \"The mission used GSLV Mk III rocket for its launch\",\n",
    "    \"Chandrayaan-3 was launched from the Satish Dhawan Space Centre in Sriharikota\",\n",
    "    \"Chandrayaan-3 was launched earlier in the year 2023\",\n",
    "]\n",
    "len(documents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-30T00:33:35.766791Z",
     "start_time": "2024-03-30T00:33:35.756803Z"
    }
   },
   "outputs": [],
   "source": [
    "model_id = \"BAAI/bge-small-en\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setting up 🤗 Huggingface\n",
    "\n",
    "We'll be using the [Huggingface Transformers](https://huggingface.co/transformers/) with PyTorch library to generate embeddings. We'll be using the same model across both libraries for a fair(er?) comparison."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-30T00:34:03.988Z",
     "start_time": "2024-03-30T00:33:37.460865Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "config.json: 100%|██████████| 684/684 [00:00<00:00, 491kB/s]\n",
      "model.safetensors: 100%|██████████| 133M/133M [00:21<00:00, 6.24MB/s] \n",
      "tokenizer_config.json: 100%|██████████| 366/366 [00:00<00:00, 4.06MB/s]\n",
      "vocab.txt: 100%|██████████| 232k/232k [00:00<00:00, 1.12MB/s]\n",
      "tokenizer.json: 100%|██████████| 711k/711k [00:00<00:00, 1.59MB/s]\n",
      "special_tokens_map.json: 100%|██████████| 125/125 [00:00<00:00, 399kB/s]\n"
     ]
    },
    {
     "data": {
      "text/plain": "torch.Size([12, 384])"
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "class HF:\n",
    "    \"\"\"\n",
    "    HuggingFace Transformer implementation of FlagEmbedding\n",
    "    Based on https://huggingface.co/BAAI/bge-base-en\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, model_id: str):\n",
    "        self.model = AutoModel.from_pretrained(model_id)\n",
    "        self.tokenizer = AutoTokenizer.from_pretrained(model_id)\n",
    "\n",
    "    def embed(self, texts: list[str]):\n",
    "        encoded_input = self.tokenizer(\n",
    "            texts, max_length=512, padding=True, truncation=True, return_tensors=\"pt\"\n",
    "        )\n",
    "        model_output = self.model(**encoded_input)\n",
    "        sentence_embeddings = model_output[0][:, 0]\n",
    "        sentence_embeddings = F.normalize(sentence_embeddings)\n",
    "        return sentence_embeddings\n",
    "\n",
    "\n",
    "hf = HF(model_id=model_id)\n",
    "hf.embed(documents).shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setting up ⚡️FastEmbed\n",
    "\n",
    "Sorry, don't have a lot to set up here. We'll be using the default model, which is Flag Embedding, same as the Huggingface model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-30T00:34:04.076422Z",
     "start_time": "2024-03-30T00:34:03.987162Z"
    }
   },
   "outputs": [],
   "source": [
    "embedding_model = TextEmbedding(model_name=model_id)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 📊 Comparison\n",
    "\n",
    "We'll be comparing the following metrics: Minimum, Maximum, Mean, across k runs. Let's write a function to do that:\n",
    "\n",
    "### 🚀 Calculating Stats"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-30T00:34:06.543782Z",
     "start_time": "2024-03-30T00:34:06.357816Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Huggingface Transformers (Average, Max, Min): (0.05358994007110596, 0.0568850040435791, 0.05029487609863281)\n",
      "FastEmbed (Average, Max, Min): (0.035953521728515625, 0.03631591796875, 0.03559112548828125)\n"
     ]
    }
   ],
   "source": [
    "import types\n",
    "\n",
    "\n",
    "def calculate_time_stats(\n",
    "    embed_func: Callable, documents: list, k: int\n",
    ") -> tuple[float, float, float]:\n",
    "    times = []\n",
    "    for _ in range(k):\n",
    "        # Timing the embed_func call\n",
    "        start_time = time.time()\n",
    "        embeddings = embed_func(documents)\n",
    "        # Force computation if embed_func returns a generator\n",
    "        if isinstance(embeddings, types.GeneratorType):\n",
    "            list(embeddings)\n",
    "\n",
    "        end_time = time.time()\n",
    "        times.append(end_time - start_time)\n",
    "\n",
    "    # Returning mean, max, and min time for the call\n",
    "    return (sum(times) / k, max(times), min(times))\n",
    "\n",
    "\n",
    "hf_stats = calculate_time_stats(hf.embed, documents, k=2)\n",
    "print(f\"Huggingface Transformers (Average, Max, Min): {hf_stats}\")\n",
    "fst_stats = calculate_time_stats(embedding_model.embed, documents, k=2)\n",
    "print(f\"FastEmbed (Average, Max, Min): {fst_stats}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 📈 Results\n",
    "\n",
    "Let's run the comparison and see the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-03-30T00:34:11.032206Z",
     "start_time": "2024-03-30T00:34:10.828410Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "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CowePRpff/01/vrrL5w7dw4DBgyAs7OzdIedl5cXgoKCMGTIEBw/fhyHDh1CREQE+vbtC2dnZwBA//79YWRkhLCwMFy4cAFr1qzBvHnzMHbsWCmPUaNGISoqCrNnz0Z8fDymTZuGkydPIiIiQut9IiIioreTVpfnypcvrzZH0us6efIk2rZtKz0vKmRCQkKwfPlyjB8/HtnZ2Rg6dCgyMjLQokULREVFwcTERFpn1apViIiIQPv27WFgYIDevXvjhx9+kJZbW1tj586dCA8Ph6+vLypWrIgpU6aozeXUrFkzrF69GpMmTcJ//vMf1KhRA5s3b+bXwhAREZFE63mavvnmG1y5cgU///wzypV7pW9heStxniaissN5moj+Hcp6niatq54TJ04gOjoaO3fuhI+PD8zNzdWWb9y4UdsuiYiIiPSe1kWTjY0NevfuXRq5EBEREektrYumZcuWlUYeRERERHpN6ykHiIiIiP6NXmkk9/r167F27VokJSUhPz9fbZkuv0qFiIiISF9ofabphx9+wMCBA+Ho6IjTp0+jcePGsLOzw/Xr19GxY8fSyJGIiIiozGldNP34449YunQp5s+fDyMjI4wfPx67du3CyJEjkZmZWRo5EhEREZU5rYumpKQkNGvWDABgamqKR48eAQA+/PBD/P7777rNjoiIiEhPaF00OTk54eHDhwCAKlWq4OjRowCefgWKlvNkEhEREf1jaF00tWvXDn/99RcAYODAgRgzZgw6dOiA999/Hz179tR5gkRERET6QOu755YuXQqVSgUACA8Ph52dHQ4fPoxu3brho48+0nmCRERERPpA66LJwMAABgb/O0HVt29f9O3bV6dJEREREembV5qnKSMjA8ePH0dqaqp01qnIgAEDdJIYERERkT7RumjasmULgoODkZWVBSsrKygUCmmZQqFg0URERERvJa0Hgn/yyScYNGgQsrKykJGRgfT0dOlRdFcdERER0dtG66Lpzp07GDlyJMzMzEojHyIiIiK9pHXRFBgYiJMnT5ZGLkRERER6S9aYpqJ5mQCgc+fO+PTTT3Hx4kX4+PigfPnyarHdunXTbYZEREREekBW0dSjRw+Nti+//FKjTaFQoLCw8LWTIiIiItI3soqm56cVICIiIvq30XpMExEREdG/keyiKSYmBt7e3lAqlRrLMjMzUbt2bezfv1+n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",
      "text/plain": "<Figure size 640x480 with 1 Axes>"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_character_per_second_comparison(\n",
    "    hf_stats: tuple[float, float, float], fst_stats: tuple[float, float, float], documents: list\n",
    "):\n",
    "    # Calculating total characters in documents\n",
    "    total_characters = sum(len(doc) for doc in documents)\n",
    "\n",
    "    # Calculating characters per second for each model\n",
    "    hf_chars_per_sec = total_characters / hf_stats[0]  # Mean time is at index 0\n",
    "    fst_chars_per_sec = total_characters / fst_stats[0]\n",
    "\n",
    "    # Plotting the bar chart\n",
    "    models = [\"HF Embed (Torch)\", \"FastEmbed\"]\n",
    "    chars_per_sec = [hf_chars_per_sec, fst_chars_per_sec]\n",
    "\n",
    "    bars = plt.bar(models, chars_per_sec, color=[\"#1f356c\", \"#dd1f4b\"])\n",
    "    plt.ylabel(\"Characters per Second\")\n",
    "    plt.title(\"Characters Processed per Second Comparison\")\n",
    "\n",
    "    # Adding the number at the top of each bar\n",
    "    for bar, chars in zip(bars, chars_per_sec):\n",
    "        plt.text(\n",
    "            bar.get_x() + bar.get_width() / 2,\n",
    "            bar.get_height(),\n",
    "            f\"{chars:.1f}\",\n",
    "            ha=\"center\",\n",
    "            va=\"bottom\",\n",
    "            color=\"#1f356c\",\n",
    "            fontsize=12,\n",
    "        )\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_character_per_second_comparison(hf_stats, fst_stats, documents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "fst",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.17"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
